Научете как да използвате React Error Boundaries с hooks за справяне с грешки при зареждане на ресурси, подобрявайки потребителското изживяване и стабилността на приложението.
Надеждно зареждане на ресурси в React: Овладяване на границите на грешките с Hooks
В съвременните уеб приложения асинхронното зареждане на ресурси е често срещана практика. Независимо дали става въпрос за извличане на данни от API, зареждане на изображения или импортиране на модули, справянето с потенциални грешки по време на зареждането на ресурси е от решаващо значение за гладкото потребителско изживяване. Границите на грешките (Error Boundaries) в React предоставят механизъм за улавяне на JavaScript грешки навсякъде в дървото на дъщерните им компоненти, регистриране на тези грешки и показване на резервен потребителски интерфейс (fallback UI), вместо да сриват цялото приложение. Тази статия разглежда как ефективно да използвате границите на грешките в комбинация с React Hooks за управление на грешки при зареждане на ресурси.
Разбиране на границите на грешките (Error Boundaries)
Преди React 16 необработените JavaScript грешки по време на рендирането на компоненти биха повредили вътрешното състояние на React и биха причинили загадъчни грешки при последващи рендирания. Границите на грешките решават този проблем, като действат като блокове за улавяне на всички грешки, които възникват в техните дъщерни компоненти. Те са React компоненти, които имплементират един или и двата от следните методи на жизнения цикъл:
static getDerivedStateFromError(error): Този статичен метод се извиква, след като е възникнала грешка в дъщерен компонент. Той получава възникналата грешка като аргумент и връща стойност за актуализиране на състоянието на компонента.componentDidCatch(error, info): Този метод на жизнения цикъл се извиква, след като е възникнала грешка в дъщерен компонент. Той получава възникналата грешка като аргумент, както и обект, съдържащ информация за това кой компонент е предизвикал грешката. Можете да го използвате за регистриране на информация за грешката.
Важно е да се отбележи, че границите на грешките улавят грешки само във фазата на рендиране, в методите на жизнения цикъл и в конструкторите на цялото дърво под тях. Те не улавят грешки за:
- Обработващи функции на събития (event handlers) (научете повече в секцията по-долу)
- Асинхронен код (напр.
setTimeoutилиrequestAnimationFramecallbacks) - Рендиране от страна на сървъра (server-side rendering)
- Грешки, възникнали в самата граница на грешките (а не в нейните дъщерни компоненти)
Граници на грешките и React Hooks: Мощна комбинация
Докато класовите компоненти традиционно се използваха за имплементиране на граници на грешките, React Hooks предлагат по-сбит и функционален подход. Можем да създадем преизползваем useErrorBoundary hook, който капсулира логиката за обработка на грешки и предоставя удобен начин за обвиване на компоненти, които могат да предизвикат грешки по време на зареждане на ресурси.
Създаване на персонализиран useErrorBoundary Hook
Ето пример за useErrorBoundary hook:
import { useState, useCallback } from 'react';
function useErrorBoundary() {
const [error, setError] = useState(null);
const resetError = useCallback(() => {
setError(null);
}, []);
const captureError = useCallback((e) => {
setError(e);
}, []);
const ErrorBoundary = useCallback(({ children, fallback }) => {
if (error) {
return fallback ? fallback : An error occurred: {error.message || String(error)};
}
return children;
}, [error]);
return { ErrorBoundary, captureError, error, resetError };
}
export default useErrorBoundary;
Обяснение:
useState: ИзползвамеuseStateза управление на състоянието на грешката. Първоначално той задава грешката наnull.useCallback: ИзползвамеuseCallback, за да мемоизираме функциитеresetErrorиcaptureError. Това е добра практика за предотвратяване на ненужни пререндирания, ако тези функции се предават като props.ErrorBoundaryкомпонент: Това е функционален компонент, създаден сuseCallback, който приемаchildrenи незадължителенfallbackprop. Ако в състоянието съществува грешка, той рендира или предоставенияfallbackкомпонент, или съобщение за грешка по подразбиране. В противен случай рендира дъщерните компоненти (children). Това действа като нашата граница на грешките. Масивът на зависимостите `[error]` гарантира, че той ще се пререндира, когато състоянието на `error` се промени.captureErrorфункция: Тази функция се използва за задаване на състоянието на грешката. Ще я извикате вtry...catchблок при зареждане на ресурси.resetErrorфункция: Тази функция изчиства състоянието на грешката, позволявайки на компонента да рендира отново своите дъщерни компоненти (потенциално опитвайки отново зареждането на ресурса).
Имплементиране на зареждане на ресурси с обработка на грешки
Сега, нека видим как да използваме този hook за справяне с грешки при зареждане на ресурси. Разгледайте компонент, който извлича потребителски данни от API:
import React, { useState, useEffect } from 'react';
import useErrorBoundary from './useErrorBoundary';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const { ErrorBoundary, captureError, error, resetError } = useErrorBoundary();
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (e) {
captureError(e);
}
};
fetchData();
}, [userId, captureError]);
if (error) {
return (
Failed to load user data. {user.name}
Email: {user.email}
{/* Other user details */}Обяснение:
- Импортираме
useErrorBoundaryhook. - Извикваме hook-а, за да получим компонента
ErrorBoundary, функциятаcaptureError, състояниетоerrorи функциятаresetError. - Вътре в
useEffecthook-а обвиваме API извикването вtry...catchблок. - Ако възникне грешка по време на API извикването, извикваме
captureError(e), за да зададем състоянието на грешката. - Ако състоянието
errorе зададено, рендираме компонентаErrorBoundary. Предоставяме персонализиранfallbackprop, който показва съобщение за грешка и бутон "Опитай отново" (Retry). Кликването върху бутона извикваresetError, за да изчисти състоянието на грешката, което задейства пререндиране и нов опит за извличане на данните. - Ако не е възникнала грешка и потребителските данни са заредени, рендираме детайлите на потребителския профил.
Справяне с различни видове грешки при зареждане на ресурси
Различните видове грешки при зареждане на ресурси може да изискват различни стратегии за обработка. Ето някои често срещани сценарии и как да се справите с тях:
Мрежови грешки
Мрежови грешки възникват, когато клиентът не може да се свърже със сървъра (напр. поради прекъсване на мрежата или неработещ сървър). Горният пример вече обработва основни мрежови грешки с помощта на `response.ok`. Може да искате да добавите по-усъвършенствано откриване на грешки, например:
//Inside the fetchData function
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
// Consider adding specific error code handling
if (response.status === 404) {
throw new Error("User not found");
} else if (response.status >= 500) {
throw new Error("Server error. Please try again later.");
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error.message === 'Failed to fetch') {
// Likely a network error
captureError(new Error('Network error. Please check your internet connection.'));
} else {
captureError(error);
}
}
В този случай можете да покажете съобщение на потребителя, указващо, че има проблем с мрежовата свързаност, и да му предложите да провери интернет връзката си.
API грешки
API грешки възникват, когато сървърът върне отговор за грешка (напр. 400 Bad Request или 500 Internal Server Error). Както е показано по-горе, можете да проверите `response.status` и да обработите тези грешки по подходящ начин.
Грешки при парсване на данни
Грешки при парсване на данни възникват, когато отговорът от сървъра не е в очаквания формат и не може да бъде парснат (напр. невалиден JSON). Можете да обработите тези грешки, като обвиете извикването на response.json() в try...catch блок:
//Inside the fetchData function
try {
const response = await fetch(`https://api.example.com/users/${userId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setUser(data);
} catch (error) {
if (error instanceof SyntaxError) {
captureError(new Error('Failed to parse data from server.'));
} else {
captureError(error);
}
}
Грешки при зареждане на изображения
За зареждане на изображения можете да използвате обработчика на събития onError на тага <img>:
function MyImage({ src, alt }) {
const { ErrorBoundary, captureError } = useErrorBoundary();
const [imageLoaded, setImageLoaded] = useState(false);
const handleImageLoad = () => {
setImageLoaded(true);
};
const handleImageError = (e) => {
captureError(new Error(`Failed to load image: ${src}`));
};
return (
Failed to load image.